﻿using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Sencha.Serialization.Serializers
{
	using Contracts;
	using System.Diagnostics;

	public sealed class ArraySerializer<ArrayT> : TypeSerializer<ArrayT>
	{
		private struct CachedSerializationMethods
		{
			public static readonly Action<IJsonWriter, ArrayT> WriteAll;
			public static readonly Func<IJsonReader, ArrayT> ReadAll;
			public static readonly Func<IJsonReader, ArrayT> ReadOne;

			static CachedSerializationMethods()
			{
				var collectionType = typeof(ArrayT);
				var isArray = collectionType.IsArray;
				var isList = collectionType.IsInstantiationOf(typeof(IList<>));
				var isCollection = collectionType.IsInstantiationOf(typeof(ICollection<>));
				var isEnumerable = collectionType.IsInstantiationOf(typeof(IEnumerable<>));
				var isEnumerableInterface = collectionType.IsInterface &&
					(collectionType == typeof(IEnumerable) ||
					(collectionType.IsGenericType && collectionType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
					(collectionType.IsGenericType && collectionType.GetGenericTypeDefinition() == typeof(ICollection<>)) ||
					(collectionType.IsGenericType && collectionType.GetGenericTypeDefinition() == typeof(IList<>)));
				var elementType =
					isArray ? collectionType.GetElementType() :
					isList ? collectionType.GetInstantiationArguments(typeof(IList<>))[0] :
					isCollection ? collectionType.GetInstantiationArguments(typeof(ICollection<>))[0] :
					isEnumerable ? collectionType.GetInstantiationArguments(typeof(IEnumerable<>))[0] :
					typeof(object);


				var readAll = new DynamicMethod("ReadAll<" + collectionType.Name + ">", collectionType, new[] { typeof(IJsonReader) }, typeof(JsonSerializer).Module, collectionType.IsNotPublic || elementType.IsNotPublic);
				readAll.DefineParameter(1, ParameterAttributes.None, "reader");
				readAll.InitLocals = true;

				var readOne = new DynamicMethod("ReadOne<" + collectionType.Name + ">", collectionType, new[] { typeof(IJsonReader) }, typeof(JsonSerializer).Module, collectionType.IsNotPublic || elementType.IsNotPublic);
				readOne.DefineParameter(1, ParameterAttributes.None, "reader");
				readOne.InitLocals = true;

				var writeAll = new DynamicMethod("WriteAll<" + collectionType.Name + ">", typeof(void), new[] { typeof(IJsonWriter), collectionType }, typeof(JsonSerializer).Module, collectionType.IsNotPublic || elementType.IsNotPublic);
				writeAll.DefineParameter(1, ParameterAttributes.None, "writer");
				writeAll.DefineParameter(2, ParameterAttributes.None, "value");
				writeAll.InitLocals = true;


				var getHierarchy = typeof(ISerializationContext).GetProperty("Hierarchy").GetGetMethod(); Debug.Assert(getHierarchy != null, "ISerializationContext::Hierarchy is missing");
				var stackPush = typeof(Stack).GetMethod("Push", new[] { typeof(object) }); Debug.Assert(stackPush != null, "Stack::Push is missing");
				var stackPop = typeof(Stack).GetMethod("Pop"); Debug.Assert(stackPop != null, "Stack::Pop is missing");


				var throwIfEof = typeof(JsonReaderExtentions).GetMethod("ThrowIfEndOfStream", new[] { typeof(IJsonReader), typeof(JsonToken) });
				var throwReaderStackImba = typeof(JsonReaderExtentions).GetMethod("ThrowStackImbalance", new[] { typeof(IJsonReader) });
				var throwWriterStackImba = typeof(JsonWriterExtentions).GetMethod("ThrowStackImbalance", new[] { typeof(IJsonWriter) });

				var allReadMethods = typeof(JsonReaderExtentions).GetMethods();
				var readMethod = allReadMethods.Where(m => m.Name.StartsWith("Read") && m.Name != "ReadMember" && m.GetParameters().Length == 2 && m.ReturnType == elementType)
					.DefaultIfEmpty(allReadMethods.Where(m => m.IsGenericMethod && m.Name == "ReadValue").Single().MakeGenericMethod(elementType))
					.SingleOrDefault();

				var allWriteMethods = typeof(JsonWriterExtentions).GetMethods();
				var writeMethod = allWriteMethods.Where(m => m.Name.StartsWith("Write") && m.Name != "WriteMember" && m.GetParameters().Length == 2 && m.GetParameters()[1].ParameterType == elementType)
					.DefaultIfEmpty(allWriteMethods.Where(m => m.IsGenericMethod && m.Name == "WriteValue").Single().MakeGenericMethod(elementType))
					.SingleOrDefault();

				{
					var nextToken = typeof(IJsonReader).GetMethod("NextToken", Type.EmptyTypes); Debug.Assert(nextToken != null, "IJsonReader::NextToken is missing");
					var getToken = typeof(IJsonReader).GetProperty("Token").GetGetMethod(); Debug.Assert(getToken != null, "IJsonReader::Token is missing");
					var getContext = typeof(IJsonReader).GetProperty("Context").GetGetMethod(); Debug.Assert(getContext != null, "IJsonReader::Context is missing");

					var listType = isArray || isEnumerableInterface ? typeof(List<>).MakeGenericType(elementType) : collectionType; Debug.Assert(listType != null, "IJsonReader::NextToken is missing");
					var listCtr = listType.GetConstructor(Type.EmptyTypes); Debug.Assert(listCtr != null, listType.Name + "::ctr() is missing");
					var listAdd = listType.GetMethod("Add", new[] { elementType });
					var listToArray = listType.GetMethod("ToArray", Type.EmptyTypes);

					if (listAdd == null)
					{
						if (isCollection)
							listAdd = typeof(ICollection<>).MakeGenericType(elementType).GetMethod("Add", new[] { elementType });
						else
							throw new InvalidOperationException(string.Format("List '{0}' does't have public Add method accepting '{1}' type as first and single argument", collectionType, elementType));
					}

					var il = readAll.GetILGenerator();
					var listLocal = il.DeclareLocal(listType);
					var contextLocal = il.DeclareLocal(typeof(Stack));

					var whileBodyLabel = il.DefineLabel();
					var whileEndLabel = il.DefineLabel();
					var whileConditionLabel = il.DefineLabel();
					var convertAndReturn = il.DefineLabel();

					il.Emit(OpCodes.Nop);
					il.Emit(OpCodes.Newobj, listCtr); // new list
					il.Emit(OpCodes.Stloc_0);
					if (!collectionType.IsValueType)
					{
						il.Emit(OpCodes.Ldarg_0); // reader
						il.Emit(OpCodes.Callvirt, getContext); // reader.Context
						il.Emit(OpCodes.Callvirt, getHierarchy); // reader.Context.Hierarchy
						il.Emit(OpCodes.Stloc_1);
						il.Emit(OpCodes.Ldloc_1);
						il.Emit(OpCodes.Ldloc_0);
						il.Emit(OpCodes.Callvirt, stackPush); // reader.Context.Hierarchy.Push(list)
					}
					il.Emit(OpCodes.Br_S, whileConditionLabel);
					il.MarkLabel(whileBodyLabel);
					il.Emit(OpCodes.Ldarg_0); // load reader
					il.Emit(OpCodes.Ldc_I4, (int)JsonToken.EndOfArray);
					il.Emit(OpCodes.Call, throwIfEof); // ThrowIfEndOfStream(reader, JsonToken.EndOfArray);
					il.Emit(OpCodes.Ldloc_0); // load list
					if (listAdd.DeclaringType.IsInterface)
						il.Emit(OpCodes.Isinst, listAdd.DeclaringType);
					il.Emit(OpCodes.Ldarg_0); // load reader
					il.Emit(OpCodes.Ldc_I4_0); // false
					il.Emit(OpCodes.Call, readMethod); // read
					if (listAdd.IsVirtual && !listAdd.IsFinal)
						il.Emit(OpCodes.Callvirt, listAdd);
					else
						il.Emit(OpCodes.Call, listAdd);
					if (listAdd.ReturnType != null && listAdd.ReturnType != typeof(void))
						il.Emit(OpCodes.Pop);
					il.Emit(OpCodes.Ldarg_0); // load reader
					il.Emit(OpCodes.Ldc_I4, (int)JsonToken.EndOfArray);
					il.Emit(OpCodes.Call, throwIfEof); // ThrowIfEndOfStream(reader, JsonToken.EndOfArray);
					il.MarkLabel(whileConditionLabel);
					il.Emit(OpCodes.Ldarg_0); // load reader
					il.Emit(OpCodes.Callvirt, nextToken); // reader.NextToken();
					il.Emit(OpCodes.Brfalse_S, whileEndLabel);
					il.Emit(OpCodes.Ldarg_0); // load reader
					il.Emit(OpCodes.Callvirt, getToken); // reader.Token;
					il.Emit(OpCodes.Ldc_I4, (int)JsonToken.EndOfArray);
					il.Emit(OpCodes.Ceq);
					il.Emit(OpCodes.Brfalse_S, whileBodyLabel);
					il.MarkLabel(whileEndLabel);
					if (!collectionType.IsValueType)
					{
						il.Emit(OpCodes.Ldloc_1); // load stack
						il.Emit(OpCodes.Callvirt, stackPop);
						il.Emit(OpCodes.Ldloc_0); // load list
						il.Emit(OpCodes.Ceq); // stack.Pop() == list
						il.Emit(OpCodes.Brtrue_S, convertAndReturn);
						il.Emit(OpCodes.Ldarg_0); // load reader
						il.Emit(OpCodes.Call, throwReaderStackImba);
					}
					il.MarkLabel(convertAndReturn);
					if (isArray)
					{
						il.Emit(OpCodes.Ldloc_0); // load list
						il.Emit(OpCodes.Call, listToArray);
					}
					else
					{
						il.Emit(OpCodes.Ldloc_0); // load list
					}
					il.Emit(OpCodes.Isinst, collectionType);
					il.Emit(OpCodes.Ret);
				}

				{
					var nextToken = typeof(IJsonReader).GetMethod("NextToken", Type.EmptyTypes); Debug.Assert(nextToken != null, "IJsonReader::NextToken is missing");
					var getToken = typeof(IJsonReader).GetProperty("Token").GetGetMethod(); Debug.Assert(getToken != null, "IJsonReader::Token is missing");
					var getContext = typeof(IJsonReader).GetProperty("Context").GetGetMethod(); Debug.Assert(getContext != null, "IJsonReader::Context is missing");

					var listType = isArray || isEnumerableInterface ? typeof(List<>).MakeGenericType(elementType) : collectionType; Debug.Assert(listType != null, "IJsonReader::NextToken is missing");
					var listCtr = listType.GetConstructor(Type.EmptyTypes); Debug.Assert(listCtr != null, listType.Name + "::ctr() is missing");
					var listAdd = listType.GetMethod("Add", new[] { elementType });
					var listToArray = listType.GetMethod("ToArray", Type.EmptyTypes);

					if (listAdd == null)
					{
						if (isCollection)
							listAdd = typeof(ICollection<>).MakeGenericType(elementType).GetMethod("Add", new[] { elementType });
						else
							throw new InvalidOperationException(string.Format("List '{0}' does't have public Add method accepting '{1}' type as first and single argument", collectionType, elementType));
					}

					var il = readOne.GetILGenerator();
					var listLocal = il.DeclareLocal(listType);
					var contextLocal = il.DeclareLocal(typeof(Stack));

					var convertAndReturn = il.DefineLabel();

					il.Emit(OpCodes.Nop);
					il.Emit(OpCodes.Newobj, listCtr);
					il.Emit(OpCodes.Stloc_0); // list = new <ListType>();

					if (!collectionType.IsValueType)
					{
						il.Emit(OpCodes.Ldarg_0); // reader
						il.Emit(OpCodes.Callvirt, getContext); // reader.Context
						il.Emit(OpCodes.Callvirt, getHierarchy); // reader.Context.Hierarchy
						il.Emit(OpCodes.Stloc_1);
						il.Emit(OpCodes.Ldloc_1);
						il.Emit(OpCodes.Ldloc_0);
						il.Emit(OpCodes.Callvirt, stackPush); // reader.Context.Hierarchy.Push(list)
					}

					il.Emit(OpCodes.Ldarg_0); // load reader
					il.Emit(OpCodes.Ldc_I4, (int)JsonToken.BeginArray);
					il.Emit(OpCodes.Call, throwIfEof); // reader.ThrowIfEndOfStream(JsonToken.BeginArray);

					il.Emit(OpCodes.Ldloc_0); // load list
					if (listAdd.DeclaringType.IsInterface)
						il.Emit(OpCodes.Isinst, listAdd.DeclaringType); // (<ListType>)list
					il.Emit(OpCodes.Ldarg_0); // load reader
					il.Emit(OpCodes.Ldc_I4_0); // advance:false
					il.Emit(OpCodes.Call, readMethod); // reader.Read(advance:false);
					
					if (listAdd.IsVirtual && !listAdd.IsFinal)
						il.Emit(OpCodes.Callvirt, listAdd); // virtual list.Add(reader.Read());
					else
						il.Emit(OpCodes.Call, listAdd); // list.Add(reader.Read());

					if (listAdd.ReturnType != null && listAdd.ReturnType != typeof(void))
						il.Emit(OpCodes.Pop);

					if (!collectionType.IsValueType)
					{
						il.Emit(OpCodes.Ldloc_1); // load stack
						il.Emit(OpCodes.Callvirt, stackPop);
						il.Emit(OpCodes.Ldloc_0); // load list
						il.Emit(OpCodes.Ceq); // stack.Pop() == list
						il.Emit(OpCodes.Brtrue_S, convertAndReturn);
						il.Emit(OpCodes.Ldarg_0); // load reader
						il.Emit(OpCodes.Call, throwReaderStackImba);
					}

					il.MarkLabel(convertAndReturn);
					if (isArray)
					{
						il.Emit(OpCodes.Ldloc_0); // load list
						il.Emit(OpCodes.Call, listToArray);
					}
					else
					{
						il.Emit(OpCodes.Ldloc_0); // load list
					}
					il.Emit(OpCodes.Isinst, collectionType);
					il.Emit(OpCodes.Ret);
				}

				{
					var getContext = typeof(IJsonWriter).GetProperty("Context").GetGetMethod(); Debug.Assert(getContext != null, "IJsonWriter::Context is missing");

					var il = writeAll.GetILGenerator();

					var idxLocal = il.DeclareLocal(typeof(int));

					var whileBodyLabel = il.DefineLabel();
					var whileConditionLabel = il.DefineLabel();
					var returnLabel = il.DefineLabel();

					if (isArray)
					{
						il.Emit(OpCodes.Nop);

						if (!collectionType.IsValueType)
						{
							il.Emit(OpCodes.Ldarg_0); // writer
							il.Emit(OpCodes.Callvirt, getContext); // writer.Context
							il.Emit(OpCodes.Callvirt, getHierarchy); // writer.Context.Hierarchy						
							il.Emit(OpCodes.Ldarg_1); // array
							il.Emit(OpCodes.Callvirt, stackPush); // writer.Context.Hierarchy.Push(array)
						}
						il.Emit(OpCodes.Ldc_I4_0);
						il.Emit(OpCodes.Stloc_0); // i = 0;
						il.Emit(OpCodes.Br_S, whileConditionLabel);
						il.MarkLabel(whileBodyLabel);
						il.Emit(OpCodes.Ldarg_0); // writer
						il.Emit(OpCodes.Ldarg_1); // array
						il.Emit(OpCodes.Ldloc_0); // i
						if (elementType.IsValueType)
						{
							il.Emit(OpCodes.Ldelema, elementType); //  T* ptr = array[i]
							il.Emit(OpCodes.Ldobj, elementType); // *ptr
						}
						else
							il.Emit(OpCodes.Ldelem_Ref); // array[i]
						il.Emit(OpCodes.Call, writeMethod); // Write(writer, array[i])
						//
						il.Emit(OpCodes.Ldloc_0);
						il.Emit(OpCodes.Ldc_I4_1);
						il.Emit(OpCodes.Add);
						il.Emit(OpCodes.Stloc_0); // i++
						il.MarkLabel(whileConditionLabel);
						il.Emit(OpCodes.Ldloc_0);
						il.Emit(OpCodes.Ldarg_1); // array
						il.Emit(OpCodes.Ldlen); // array.Length
						il.Emit(OpCodes.Conv_I4);
						il.Emit(OpCodes.Clt); // i < array.Length
						il.Emit(OpCodes.Brtrue_S, whileBodyLabel);
						if (!collectionType.IsValueType)
						{
							il.Emit(OpCodes.Ldarg_0); // writer
							il.Emit(OpCodes.Callvirt, getContext); // writer.Context
							il.Emit(OpCodes.Callvirt, getHierarchy); // writer.Context.Hierarchy						
							il.Emit(OpCodes.Callvirt, stackPop); // writer.Context.Hierarchy.Pop()
							il.Emit(OpCodes.Ldarg_1); // array
							il.Emit(OpCodes.Ceq); // array
							il.Emit(OpCodes.Brtrue_S, returnLabel);
							il.Emit(OpCodes.Ldarg_0); // load writer
							il.Emit(OpCodes.Call, throwWriterStackImba);
						}
						il.MarkLabel(returnLabel);
						il.Emit(OpCodes.Ret);
					}
					else if (isList)
					{
						var genCollType = typeof(ICollection<>).MakeGenericType(elementType);
						var genListType = typeof(IList<>).MakeGenericType(elementType);
						var listType = collectionType.IsGenericType && collectionType.GetGenericTypeDefinition() == typeof(List<>) ? collectionType :
						isList ? collectionType = genListType : null; Debug.Assert(listType != null);

						var listCount = listType.GetMethod("get_Count") ?? genCollType.GetMethod("get_Count"); Debug.Assert(listCount != null);
						var listGetItem = listType.GetMethod("get_Item"); Debug.Assert(listGetItem != null);

						il.Emit(OpCodes.Nop);
						if (!collectionType.IsValueType)
						{
							il.Emit(OpCodes.Ldarg_0); // writer
							il.Emit(OpCodes.Callvirt, getContext); // writer.Context
							il.Emit(OpCodes.Callvirt, getHierarchy); // writer.Context.Hierarchy						
							il.Emit(OpCodes.Ldarg_1); // array
							il.Emit(OpCodes.Callvirt, stackPush); // writer.Context.Hierarchy.Push(array)
						}
						il.Emit(OpCodes.Ldc_I4_0);
						il.Emit(OpCodes.Stloc_0); // i = 0;
						il.Emit(OpCodes.Br_S, whileConditionLabel);
						il.MarkLabel(whileBodyLabel);
						il.Emit(OpCodes.Ldarg_0); // writer
						il.Emit(OpCodes.Ldarg_1); // list
						il.Emit(OpCodes.Ldloc_0); // i
						if (listGetItem.DeclaringType != collectionType)
							il.Emit(OpCodes.Isinst, listGetItem.DeclaringType); // (list as IList<T>)
						if (listGetItem.IsVirtual && !listGetItem.IsFinal)
							il.Emit(OpCodes.Callvirt, listGetItem); //call list[i]
						else
							il.Emit(OpCodes.Call, listGetItem); // virtcall list[i];
						il.Emit(OpCodes.Call, writeMethod); // Write(writer, list[i])
						//
						il.Emit(OpCodes.Ldloc_0);
						il.Emit(OpCodes.Ldc_I4_1);
						il.Emit(OpCodes.Add);
						il.Emit(OpCodes.Stloc_0); // i++
						il.MarkLabel(whileConditionLabel);
						il.Emit(OpCodes.Ldloc_0); // i
						il.Emit(OpCodes.Ldarg_1); // list
						if (listCount.DeclaringType != collectionType)
							il.Emit(OpCodes.Isinst, listCount.DeclaringType); // (list as IList<T>)
						if (listGetItem.IsVirtual && !listGetItem.IsFinal)
							il.Emit(OpCodes.Callvirt, listCount); //callvirt list.Count
						else
							il.Emit(OpCodes.Call, listCount); //call list.Count
						il.Emit(OpCodes.Conv_I4); // (int)list.Count
						il.Emit(OpCodes.Clt); // i < list.Count
						il.Emit(OpCodes.Brtrue_S, whileBodyLabel);
						if (!collectionType.IsValueType)
						{
							il.Emit(OpCodes.Ldarg_0); // writer
							il.Emit(OpCodes.Callvirt, getContext); // writer.Context
							il.Emit(OpCodes.Callvirt, getHierarchy); // writer.Context.Hierarchy						
							il.Emit(OpCodes.Callvirt, stackPop); // writer.Context.Hierarchy.Pop()
							il.Emit(OpCodes.Ldarg_1); // array
							il.Emit(OpCodes.Ceq); // array
							il.Emit(OpCodes.Brtrue_S, returnLabel);
							il.Emit(OpCodes.Ldarg_0); // load writer
							il.Emit(OpCodes.Call, throwWriterStackImba);
						}
						il.MarkLabel(returnLabel);
						il.Emit(OpCodes.Ret);
					}
					else
					{
						var enumType = collectionType.IsInstantiationOf(typeof(IEnumerable<>)) ? typeof(IEnumerable<>).MakeGenericType(elementType) : typeof(IEnumerable); Debug.Assert(enumType != null);
						var getEnumtor = enumType.GetMethod("GetEnumerator"); Debug.Assert(getEnumtor != null);
						var enumtorType = getEnumtor.ReturnType; Debug.Assert(enumtorType != null);
						var enumMoveNext = typeof(IEnumerator).GetMethod("MoveNext"); Debug.Assert(enumMoveNext != null);
						var enumGetCurrent = enumtorType.GetMethod("get_Current"); Debug.Assert(enumGetCurrent != null);

						var enumLocal = il.DeclareLocal(enumtorType);

						il.Emit(OpCodes.Nop);
						if (!collectionType.IsValueType)
						{
							il.Emit(OpCodes.Ldarg_0); // writer
							il.Emit(OpCodes.Callvirt, getContext); // writer.Context
							il.Emit(OpCodes.Callvirt, getHierarchy); // writer.Context.Hierarchy						
							il.Emit(OpCodes.Ldarg_1); // array
							il.Emit(OpCodes.Callvirt, stackPush); // writer.Context.Hierarchy.Push(array)
						}
						il.Emit(OpCodes.Ldarg_1); // enum
						if (getEnumtor.DeclaringType != collectionType)
							il.Emit(OpCodes.Isinst, getEnumtor.DeclaringType); // (enum as IEnumerable)
						if (getEnumtor.IsVirtual && !getEnumtor.IsFinal)
							il.Emit(OpCodes.Callvirt, getEnumtor); // enum.GetEnumerator()
						else
							il.Emit(OpCodes.Call, getEnumtor); // enum.GetEnumerator()
						il.Emit(OpCodes.Stloc_1); // enumerator = enum.GetEnumerator()
						il.Emit(OpCodes.Br_S, whileConditionLabel);
						il.MarkLabel(whileBodyLabel);
						il.Emit(OpCodes.Ldarg_0); // writer
						il.Emit(OpCodes.Ldloc_1); // enumerator
						il.Emit(OpCodes.Callvirt, enumGetCurrent); // enumerator.Current
						if (enumGetCurrent.ReturnType != elementType)
						{
							if (enumGetCurrent.ReturnType.IsValueType)
								il.Emit(OpCodes.Box, enumGetCurrent.ReturnType);
							il.Emit(OpCodes.Castclass, elementType); // (T)enumerator.Current
						}
						il.Emit(OpCodes.Call, writeMethod); // Write<T>(writer, enumerator.Current)
						il.MarkLabel(whileConditionLabel);
						il.Emit(OpCodes.Ldloc_1); // enumerator
						il.Emit(OpCodes.Callvirt, enumMoveNext); // enumerator.MoveNext()
						il.Emit(OpCodes.Brtrue_S, whileBodyLabel);
						if (!collectionType.IsValueType)
						{
							il.Emit(OpCodes.Ldarg_0); // writer
							il.Emit(OpCodes.Callvirt, getContext); // writer.Context
							il.Emit(OpCodes.Callvirt, getHierarchy); // writer.Context.Hierarchy						
							il.Emit(OpCodes.Callvirt, stackPop); // writer.Context.Hierarchy.Pop()
							il.Emit(OpCodes.Ldarg_1); // array
							il.Emit(OpCodes.Ceq); // array
							il.Emit(OpCodes.Brtrue_S, returnLabel);
							il.Emit(OpCodes.Ldarg_0); // load writer
							il.Emit(OpCodes.Call, throwWriterStackImba);
						}
						il.MarkLabel(returnLabel);
						il.Emit(OpCodes.Ret);
					}
				}

				ReadOne = (Func<IJsonReader, ArrayT>)readOne.CreateDelegate(typeof(Func<IJsonReader, ArrayT>));
				ReadAll = (Func<IJsonReader, ArrayT>)readAll.CreateDelegate(typeof(Func<IJsonReader, ArrayT>));
				WriteAll = (Action<IJsonWriter, ArrayT>)writeAll.CreateDelegate(typeof(Action<IJsonWriter, ArrayT>));
			}
		}

		private Type m_arrayType;
		private Type m_elementType;

		public ArraySerializer(TypeDescription enumerableType)
		{
			if (enumerableType == null)
				throw new ArgumentNullException("enumerableType");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			this.TypeDescription = enumerableType;

			m_arrayType = enumerableType.GetClrType();
			m_elementType = GetElementType(m_arrayType);

			if (m_elementType == null)
				throw new Exceptions.TypeContractViolation(this.GetType(), enumerableType, "be enumerable");
		}

		public override ArrayT Deserialize(IJsonReader reader)
		{
			if (reader == null)
				throw new ArgumentNullException("reader");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (reader.Token == JsonToken.Null)
				return default(ArrayT);

			// Array intercharge
			if (reader.Token != JsonToken.BeginArray && (reader.Context.Features & Features.ArrayInterchange) != 0)
				return CachedSerializationMethods.ReadOne(reader);

			if (reader.Token != JsonToken.BeginArray)
				throw new Exceptions.UnexpectedToken(reader, JsonToken.BeginArray);

			var items = CachedSerializationMethods.ReadAll(reader);

			if (reader.IsEndOfStream())
				throw new Exceptions.UnexpectedToken(reader, JsonToken.EndOfArray);

			return items;
		}
		public override void Serialize(IJsonWriter writer, ArrayT value)
		{
			if (writer == null)
				throw new ArgumentNullException("writer");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (value == null)
			{
				writer.WriteNull();
				return;
			}

			writer.Write(JsonToken.BeginArray);
			CachedSerializationMethods.WriteAll(writer, value);
			writer.Write(JsonToken.EndOfArray);
		}

		private Type GetElementType(Type arrayType)
		{
			if (arrayType == null)
				throw new ArgumentNullException("arrayType");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var elementType = (Type)null;
			if (arrayType.IsArray)
			{
				elementType = arrayType.GetElementType();
				return elementType;
			}

			if (arrayType.IsInstantiationOf(typeof(IEnumerable<>)))
			{
				if (arrayType.HasMultipleInstantiations(typeof(IEnumerable<>)))
					throw new Exceptions.TypeContractViolation(this.GetType(), this.TypeDescription, "have only one generic IEnumerable interface");

				elementType = arrayType.GetInstantiationArguments(typeof(IEnumerable<>))[0];
			}

			if (elementType == null && typeof(IEnumerable).IsAssignableFrom(arrayType))
				elementType = typeof(object);
			else if (elementType == null)
				throw new Exceptions.TypeContractViolation(this.GetType(), this.TypeDescription, "be enumerable");

			return elementType;
		}

		public override string ToString()
		{
			return string.Format("array of {1}, {0}", m_arrayType, m_elementType);
		}
	}
}
